﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.ComponentModel;
using QuickTimeWpfLib;
using WpfAnimatedGif;
using System.Drawing.Imaging;
using System.Threading;


using Emgu.CV;
using Emgu.CV.Structure;

namespace RVBike
{
	/// <summary>
	/// Interaction logic for MainGamePage.xaml
	/// </summary>
    public partial class MainGamePage : UserControl, ISwitchable, INotifyPropertyChanged
	{
        private GameSetting _setting;
        private DispatcherTimer _timer;
        private RVMovieController _controller;
        private int _current_map_level;
        private double _current_speed;
        private int _elapse_time;

        private DispatcherTimer _webcam_timer;
        private DispatcherTimer _reposition_timer;

        private bool _gameStarted = false;
        private bool _gameFinished = false;
        private RVRoom _room = null;

        private int _lastUpdateState = 0;

        private Size _originalMovieSize;

        private SynchronizationContext _context = SynchronizationContext.Current;

        // For testing only, handle keyboard event
        private bool _keydown;

        private List<Canvas> _cameraList;

		public MainGamePage()
		{
			this.InitializeComponent();
            LayoutRoot.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            LayoutRoot.Arrange(new Rect(0, 0, LayoutRoot.DesiredSize.Width, LayoutRoot.DesiredSize.Height));

            _timer = new DispatcherTimer();
            _timer.Tick += new EventHandler(OnTimer);
            _timer.Interval = new TimeSpan(300000);

            WebcamController.Instance().Init();

            _webcam_timer = new DispatcherTimer();
            _webcam_timer.Tick += new EventHandler(OnWebcamTimer);
            _webcam_timer.Interval = new TimeSpan(1000000);

            _reposition_timer = new DispatcherTimer();
            _reposition_timer.Tick += new EventHandler(OnReposition);
            _reposition_timer.Interval = new TimeSpan(3000000);

            //_current_speed = 0.0; ///< KM ... for demo usage
            this.Speed = 0.0;

            this.GaugeImpl.DataContext = this;

            // Initialize multilanguage strings
            btn_newgame.Content = MultiLanguage.Instance().Translate("new_game");
            btn_quitgame.Content = MultiLanguage.Instance().Translate("quit_game");
            btn_savegame.Content = MultiLanguage.Instance().Translate("save_game");

            MyLocation.Content = MultiLanguage.Instance().Translate("me");

            dummy_distance.Content = MultiLanguage.Instance().Translate("distance");
            dummy_m.Content = MultiLanguage.Instance().Translate("distance_unit");
            dummy_slope.Content = MultiLanguage.Instance().Translate("slope");
            dummy_rospeed.Content = MultiLanguage.Instance().Translate("rotational_speed");
            dummy_rpm.Content = MultiLanguage.Instance().Translate("rotational_speed_unit");
            dummy_calorie.Content = MultiLanguage.Instance().Translate("calorie");
            dummy_cal.Content = MultiLanguage.Instance().Translate("calorie_unit");
            dummy_power.Content = MultiLanguage.Instance().Translate("power");
            dummy_watt.Content = MultiLanguage.Instance().Translate("power_unit");

            _cameraList = new List<Canvas>();
            _cameraList.Add(Canvas_User_1);
            _cameraList.Add(Canvas_User_2);
            _cameraList.Add(Canvas_User_3);
            _cameraList.Add(Canvas_User_4);
            _cameraList.Add(Canvas_User_5);
            _cameraList.Add(Canvas_User_6);
            _cameraList.Add(Canvas_User_7);
            _cameraList.Add(Canvas_User_8);
            _cameraList.Add(Canvas_User_9);
            _cameraList.Add(Canvas_User_10);
		}

        ~MainGamePage()
        {
            _controller = null;
        }

        private void RegisterCallback()
        {
            RVGameController.Instance().ClientStateChangeHandler += OnReceiveCSChanged;
            RVGameController.Instance().StartPlayingHandler += OnReceiveStartPlaying;
        }

        private void UnregisterCallback()
        {
            RVGameController.Instance().ClientStateChangeHandler -= OnReceiveCSChanged;
            RVGameController.Instance().StartPlayingHandler -= OnReceiveStartPlaying;
        }
		
		#region ISwitchable Members
        public void UtilizeState(object state)
        {
            _setting = state as GameSetting;
            _controller = new RVMovieController(_setting.SelectedStage, MainMovie.QTControl);

            // Character animation
            Character aChar = CharacterManager.Instance().GetCharByName(UserSetting.Instance().CharName);
            var image = new BitmapImage();
            image.BeginInit();
            image.UriSource = new Uri(aChar.GifPath);
            image.EndInit();
            ImageBehavior.SetAnimatedSource(CharAnimateImpl, image);

            // Default to 1 ... Only level one can be used in game
            _current_map_level = 0;

            RVMap map = _setting.SelectedStage.GetMap(_current_map_level);
            GameMap.ImageSource = new BitmapImage(new Uri(map.MapPath));
            GameMap.Rect = new Rect(0, 0, map.Width, map.Height);
            GameMapBrush.Viewbox = new Rect(0, 0, 0, 0);

            _room = RVGameController.Instance().QueryRoomInformation();

            //panel_mapview.Visibility = Visibility.Collapsed;


            if (_setting.GameType == GameType.Lan)
            {
                RegisterCallback();
            }
            
            if (_setting.GameType == GameType.Single)
            {
                panel_user.Visibility = Visibility.Collapsed;
            }
            else
            {
                //int t_clientNum = _room.CurrentUser;
                int t_clientNum = 3;
                double t_cameraPanel_H = LayoutRoot.ActualHeight - 80;
                double t_camera_H = Math.Min(75, (t_cameraPanel_H - (t_clientNum + 1) * 10) / t_clientNum);

                for (int i = 0; i < 10; i++)
                {
                    _cameraList[i].Height = t_camera_H;
                    _cameraList[i].Width = t_camera_H * 4 / 3;

                    if (i >= t_clientNum)
                        _cameraList[i].Visibility = Visibility.Collapsed;
                }
            }

            UpdateGameState();
        }

        private void OnReposition(object sender, EventArgs e)
        {
            double W1 = panel_streetview.ActualWidth;
            double W2 = MainMovie.Movie.MovieSize.Width;
            double W3 = (W1 - W2) * 0.5;

            PopMap.VerticalOffset = MapView.ActualHeight;

            CharAnimation.HorizontalOffset = W2/2 - CharAnimateImpl.ActualWidth / 2;
            CharAnimation.VerticalOffset = -CharAnimateImpl.Height;

            Gauge.HorizontalOffset = W2 - GaugeImpl.Radius + 20;
            Gauge.VerticalOffset = 15 - GaugeImpl.Radius;

            // Workaround to force WPF update popup position
            CharAnimation.VerticalOffset++;
            CharAnimation.VerticalOffset--;

            Gauge.VerticalOffset++;
            Gauge.VerticalOffset--;

            PopMap.VerticalOffset++;
            PopMap.VerticalOffset--;
        }

        public void WindowLocationChanged(double x, double y)
        {

        }

        private void OnButtonClick(object sender, System.Windows.RoutedEventArgs e)
        {
        	// TODO: Add event handler implementation here.
			if (sender == btn_newgame)
			{
                if (_setting.GameType == GameType.Lan)
                {
                    RVGameController.Instance().LeaveRoom();
                    UnregisterCallback();
                }

                _timer.Stop();
                _webcam_timer.Stop();
                _reposition_timer.Stop();
                WebcamController.Instance().Unint();
                if (_controller != null) _controller.Uninit();

				RVSwitcher.Switch(new SinglePlayerPage(false));
			}
			else if (sender == btn_savegame)
			{
                SaveState ss = new SaveState()
                {
                    GameMode = _setting.GameMode,
                    StageName = _setting.SelectedStage.Name,
                    GameSpeed = _setting.GameSpeed,
                    Mileage = _controller.MovieCurrentMileage,
                    Time = _elapse_time
                };

                _timer.Stop();
                _webcam_timer.Stop();
                _reposition_timer.Stop();
                WebcamController.Instance().Unint();
                if (_controller != null) _controller.Uninit();

                SaveManager.Instance().AddSave(ss);

                RVSwitcher.Switch(new SinglePlayerPage(false));
			}
			else if (sender == btn_quitgame)
			{
                if (_setting.GameType == GameType.Lan)
                {
                    RVGameController.Instance().LeaveRoom();
                    UnregisterCallback();
                }

                _timer.Stop();
                _webcam_timer.Stop();
                _reposition_timer.Stop();
                WebcamController.Instance().Unint();
                if (_controller != null) _controller.Uninit();

				RVSwitcher.Switch(new WelcomePage());
			}
        }

        private void OnMouseClick_User(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
        	// TODO: Add event handler implementation here.
        }
        #endregion

        private void OnMovieStatusUpdate(object sender, StatusUpdateEventArgs e)
        {
            if (e.StatusCode == StatusCode.MovieLoadStateComplete)
            {
                // Adjust movie size
                _originalMovieSize = MainMovie.Movie.MovieSize;
                MainMovie.ScaleX = (float)(Canvas_StreetView.ActualWidth / _originalMovieSize.Width);
                MainMovie.ScaleY = (float)(Canvas_StreetView.ActualHeight / _originalMovieSize.Height);

                // Enable timer after movie is ready!!
                _timer.Start();
                _webcam_timer.Start();

                if (_setting.GameType == GameType.Lan)
                {
                    // Update state
                    RVGameController.Instance().UpdateState(RVNetworkEnv.CLIENT_STATE_LOADED, 0.0f, "0,0");

                    // wait for server to notify starting
                }
                else
                {
                    // Start playing right now!
                    _gameStarted = true;
                }
            }
        }

        private void OnMovieError(object sender, QuickTimeWpfLib.ErrorEventArgs e)
        {

        }

        private void OnReceiveStartPlaying()
        {
            // Update state
            RVGameController.Instance().UpdateState(RVNetworkEnv.CLIENT_STATE_START, 0.0f, "0,0");

            // Start playing now!!
            _gameStarted = true;

            System.Diagnostics.Trace.WriteLine("All clients are ready, start playing!!");
        }

        private void OnReceiveCSChanged(string ip, float mileage, string state, string gps)
        {
            int index = 0;
            for (; index < _room.Clients.Count; ++index)
            {
                if (_room.Clients[index].IP == ip)
                {
                    break;
                }
            }

            if (index < _room.Clients.Count)
            {
                _room.Clients[index].Mileage = mileage;
                _room.Clients[index].State = state;

                // [TODO] : Update map position
                System.Diagnostics.Trace.WriteLine(String.Format("Client[{0}] : mileage = {1}, gps = {2}",
                    _room.Clients[index].Name, mileage, gps));
            }
            else
            {
                // something wrong
                System.Diagnostics.Trace.WriteLine("Receive state update message but cannot find such user!!");
            }
        }

        private void OnKeyUp(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Up)
            {
                _keydown = false;
                this.Speed = 0.0;

                ImageBehavior.GetAnimationController(CharAnimateImpl).Pause();
            }
        }

        private void OnKeyDown(object sender, KeyEventArgs e)
        {
            if (_gameStarted && e.Key == Key.Up)
            {
                _keydown = true;
                this.Speed = 300.0;

                ImageBehavior.GetAnimationController(CharAnimateImpl).Play();
            }
        }

        private void OnTimer(object sender, EventArgs e)
        {
            if (_gameStarted && _setting.GameType == GameType.Lan)
            {
                int now = System.Environment.TickCount;
                if (now - _lastUpdateState > 1000)
                {
                    // Update state every 1 second
                    RVGameController.Instance().UpdateState(
                        _gameFinished ? RVNetworkEnv.CLIENT_STATE_FINISH : RVNetworkEnv.CLIENT_STATE_START,
                        _controller.MovieCurrentMileage,
                        String.Format("{0},{1}", _controller.GPSX, _controller.GPSY));

                    _lastUpdateState = now;
                }
            }


            if (_keydown)
            {
                PlayOneFrame();
            }
        }

        private void OnWebcamTimer(object sender, EventArgs e)
        {
            using (Image<Bgr, byte> frame = WebcamController.Instance().GetCaptureImage())
            {
                if (frame != null)
                {
                    this._context.Send(o =>
                    {
                        using (var stream = new MemoryStream())
                        {
                            // My way to display frame 
                            frame.Bitmap.Save(stream, ImageFormat.Bmp);

                            BitmapImage bitmap = new BitmapImage();
                            bitmap.BeginInit();
                            bitmap.StreamSource = new MemoryStream(stream.ToArray());
                            bitmap.EndInit();

                            //webcam.Source = bitmap;
                            //Canvas_User_1.Background
                            //ImageSource src = new 
                            //Brush b = new ImageBrush();

                            ImageBrush ib = new ImageBrush();
                            ib.ImageSource = bitmap;
                            Canvas_User_1.Background = ib;
                        }

                    },
                        null);
                }
            }
        }

        public double Speed
        {
            get
            {
                if (_keydown)
                {
                    return _current_speed;
                }

                return 0.0;
            }

            set
            {
                _current_speed = value;
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("Speed"));
                }
            }
        }

        private void PlayOneFrame()
        {
            float interval = _timer.Interval.Milliseconds / 1000.0f;
            float speed = (float)_setting.GameSpeed * (float)this.Speed * 100000.0f / 60.0f / 60.0f;

            if (!_controller.GoMileage(_controller.MovieCurrentMileage + speed * interval))
            {
                // Reach end - do something like switch to score page
            }

            UpdateGameState();
        }

        private void UpdateGameState()
        {
            if (_controller == null) return;

            UpdateGameMap();

            label_distance.Content = String.Format("{0:0.00}", _controller.MovieCurrentMileage / 100.0); ;
            label_slope.Content = _controller.Slope.ToString();
        }

        private void UpdateGameMap()
        {
            if (_controller == null) return;

            double gx = _controller.GPSX;
            double gy = _controller.GPSY;
            RVMap map = _setting.SelectedStage.GetMap(_current_map_level);

            double startX = map.MapEndX + (gx - _setting.SelectedStage.GPSEndX) * map.ScaleX + 10;
            double startY = map.MapEndY + (_setting.SelectedStage.GPSEndY - gy) * map.ScaleY + 5;
            double vx = MapView.Width/2;
            double vy = MapView.Height/2;
            GameMapBrush.Viewbox = new Rect(startX - vx, startY - vy, 0, 0); 
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            MyLocation.Margin = new Thickness(MapView.Width/2-5, MapView.Height/2-10, 0.0, 0.0);

            OnReposition(null, null);
            CharAnimation.IsOpen = true;
            Gauge.IsOpen = true;
            _reposition_timer.Start();
        }

        private void LayoutRoot_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Size t_sizeC = new Size(panel_streetview.ActualWidth, panel_streetview.ActualHeight);
            Size t_work = new Size(t_sizeC.Width, t_sizeC.Width * 9 / 16);

            if (t_work.Height > t_sizeC.Height)
            {
                t_work.Height = t_sizeC.Height;
                t_work.Width = t_work.Height * 16 / 9;
            }
            Canvas_StreetView.Margin = new Thickness((t_sizeC.Width - t_work.Width) / 2, (t_sizeC.Height - t_work.Height) / 2, 0.0, 0.0);

            // Adjust movie size
            MainMovie.ScaleX = (float)(t_work.Width / _originalMovieSize.Width);
            MainMovie.ScaleY = (float)(t_work.Height / _originalMovieSize.Height);
            
            //System.Diagnostics.Trace.WriteLine(String.Format("Movie size = {0}, {1}", s.Width, s.Height));
            //System.Diagnostics.Trace.WriteLine(String.Format("Movie scale = {0}, {1}", MainMovie.ScaleX, MainMovie.ScaleY));
        }
	}
}